package com.basker.pisces.core.query.builder;

import java.util.Date;
import java.util.List;

import org.springframework.util.Assert;

import com.basker.pisces.core.operator.CompareOperatorConstants;
import com.basker.pisces.core.query.criteria.Criterion;
import com.basker.pisces.core.query.meta.FieldDesc;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.repository.IBeanRepository;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.exception.ExceptionFactory;

/**
 * 条件构建器，提供多种静态方法构建条件,支持的符号定义在
 * {@link Operator}枚举中。CriterionBuilder用于{@link IBeanRepository}和{@link IDataRepository}接口的查询方法。
 *
 * @author hangwen
 * @see #create(String, Operator, Object)
 * @see #create(String, String, Object)
 * @see #and(CriterionBuilder...)
 * @see #or(CriterionBuilder...)
 */
public class CriterionBuilder {

    protected static final int And = -1;

    protected static final int Or = -2;
    Object value;
    private Operator operator;
    private String field;

    protected CriterionBuilder() {

    }

    private CriterionBuilder(String field, Operator operator, Object value) {
        Assert.hasText(field, "parameter 'field' is required");
        Assert.notNull(operator, "parameter 'operator' is required");

        this.field = field;
        this.operator = operator;
        this.value = value;
    }

    /**
     * 将多个条件用and拼接
     *
     * @param builders
     * @return
     */
    public static CriterionBuilder and(CriterionBuilder... builders) {
        return new CriteriaBuilder(builders, And);
    }

    /**
     * 将多个条件用and拼接
     *
     * @param builders
     * @return
     */
    public static CriterionBuilder and(List<CriterionBuilder> builders) {
        return new CriteriaBuilder(builders, And);
    }

    public static CriterionBuilder begin(String field, Object value) {
        return new CriterionBuilder(field, Operator.Begin, value);
    }

    public static CriterionBuilder between(String field, Object start, Object end) {
        return new CriterionBuilder(field, Operator.Between, new Object[]{start, end});
    }

    /**
     * 构建条件对象
     *
     * @param field    字段
     * @param operator 符号
     * @param value    条件值
     * @return
     */
    public static CriterionBuilder create(String field, Operator operator, Object value) {
        return new CriterionBuilder(field, operator, value);
    }

    /**
     * 构建条件对象
     *
     * @param field    字段
     * @param operator 符号：如下所示，大小写忽略
     *                 <li>=
     *                 <li><>、!=
     *                 <li>>、>=
     *                 <li><、<=
     *                 <li>between
     *                 <li>like、not like
     *                 <li>begin、begin with
     *                 <li>not begin、not begin with
     *                 <li>end、end with
     *                 <li>not end、not end with
     *                 <li>in、not in
     *                 <li>is null、is not null
     * @param value    条件值
     * @return
     */
    public static CriterionBuilder create(String field, String operator, Object value) {
        return new CriterionBuilder(field, Operator.parse(operator), value);
    }

    public static CriterionBuilder end(String field, Object value) {
        return new CriterionBuilder(field, Operator.End, value);
    }

    public static CriterionBuilder equal(String field, Object value) {
        return new CriterionBuilder(field, Operator.Equal, value);
    }

    public static CriterionBuilder greater(String field, Object value) {
        return new CriterionBuilder(field, Operator.Greater, value);
    }

    public static CriterionBuilder greaterOrEqual(String field, Object value) {
        return new CriterionBuilder(field, Operator.GreaterEqual, value);
    }

    public static CriterionBuilder in(String field, Object[] value) {
        return new CriterionBuilder(field, Operator.In, value);
    }

    public static CriterionBuilder isNotNull(String field) {
        return new CriterionBuilder(field, Operator.IsNotNull, Criterion.NO_CRITERION_VALUE);
    }

    public static CriterionBuilder isNull(String field) {
        return new CriterionBuilder(field, Operator.IsNull, Criterion.NO_CRITERION_VALUE);
    }

    public static CriterionBuilder less(String field, Object value) {
        return new CriterionBuilder(field, Operator.Less, value);
    }

    public static CriterionBuilder lessOrEqual(String field, Object value) {
        return new CriterionBuilder(field, Operator.LessEqual, value);
    }

    public static CriterionBuilder like(String field, Object value) {
        return new CriterionBuilder(field, Operator.Like, value);
    }

    public static CriterionBuilder notEnd(String field, Object value) {
        return new CriterionBuilder(field, Operator.NotEnd, value);
    }

    public static CriterionBuilder notEqual(String field, Object value) {
        return new CriterionBuilder(field, Operator.NotEqual, value);
    }

    public static CriterionBuilder notIn(String field, Object[] value) {
        return new CriterionBuilder(field, Operator.NotIn, value);
    }

    // public static CriterionBuilder exist(String childTable,
    // CriterionBuilder... builders) {
    // return new ChildFilterBuilder(childTable, filterBuilders);
    // }

    public static CriterionBuilder notLike(String field, Object value) {
        return new CriterionBuilder(field, Operator.NotLike, value);
    }

    public static CriterionBuilder notbegin(String field, Object value) {
        return new CriterionBuilder(field, Operator.NotBegin, value);
    }

    /**
     * 将多个条件用or拼接
     *
     * @param builders
     * @return
     */
    public static CriterionBuilder or(CriterionBuilder... builders) {
        return new CriteriaBuilder(builders, Or);
    }

    /**
     * 将多个条件用or拼接
     *
     * @param builders
     * @return
     */
    public static CriterionBuilder or(List<CriterionBuilder> builders) {
        return new CriteriaBuilder(builders, Or);
    }

    public Criterion createCriterion(QueryMeta queryMeta) {
        FieldDesc fieldDesc = queryMeta.getField(this.field, true);
        return createCriterion(fieldDesc);
    }

    public String getField() {
        return field;
    }

    protected void setField(String field) {
        this.field = field;
    }

    public Operator getOperator() {
        return operator;
    }

    public Object getValue() {
        return value;
    }

    private static String getRealOperatorId(Class<?> fieldType, Operator operator) {

        boolean isNumber = Number.class.isAssignableFrom(fieldType);
        boolean isDate = Date.class.isAssignableFrom(fieldType);

        switch (operator) {
            case Equal:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_EQUAL;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_EQUAL;
                } else {
                    return CompareOperatorConstants.STRING_EQUAL;
                }
            case NotEqual:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_NOT_EQUAL;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_NOT_EQUAL;
                } else {
                    return CompareOperatorConstants.STRING_NOT_EQUAL;
                }
            case Greater:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_GREATER;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_GREATER;
                }

                break;
            case Less:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_LESS;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_LESS;
                }

                break;
            case GreaterEqual:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_GREATER_EQUAL;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_GREATER_EQUAL;
                }

                break;
            case LessEqual:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_LESS_EQUAL;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_LESS_EQUAL;
                }

                break;
            case Like:
                return CompareOperatorConstants.STRING_LIKE;
            case NotLike:
                return CompareOperatorConstants.STRING_NOT_LIKE;
            case Begin:
                return CompareOperatorConstants.STRING_BEGIN;
            case NotBegin:
                return CompareOperatorConstants.STRING_NOT_BEGIN;
            case End:
                return CompareOperatorConstants.STRING_END;
            case NotEnd:
                return CompareOperatorConstants.STRING_NOT_END;
            case In:
                return CompareOperatorConstants.IN;
            case NotIn:
                return CompareOperatorConstants.NOT_IN;
            case IsNull:
                return CompareOperatorConstants.IS_NULL;
            case IsNotNull:
                return CompareOperatorConstants.IS_NOT_NULL;
            case Between:
                if (isNumber) {
                    return CompareOperatorConstants.NUMBER_BETWEEN;
                } else if (isDate) {
                    return CompareOperatorConstants.DATE_BETWEEN;
                }
        }

        throw ExceptionFactory.createRuntimeException("CriterionBuilder.getReailOperatorId-001",
                "filterOp is error!field type:{0},filter operator:{1}", fieldType.getName(), operator);
    }

    private Criterion createCriterion(FieldDesc fieldDesc) {
        String operatorId = getRealOperatorId(fieldDesc.getValueType(), this.operator);

        Criterion criterion = new Criterion();

        criterion.setFieldId(fieldDesc.getId());
        criterion.setFieldName(fieldDesc.getName());
        criterion.setOperatorId(operatorId);
        criterion.setValue(this.value);

        return criterion;
    }

    /**
     * 查询条件可支持的符号
     *
     * @author hangwen
     */
    public static enum Operator {
        /**
         * 等于
         */
        Equal("="),

        /**
         * 不等于
         */
        NotEqual("<>", "!="),

        /**
         * 大于
         */
        Greater(">"),

        /**
         * 小于
         */
        Less("<"),

        /**
         * 大于等于
         */
        GreaterEqual(">="),

        /**
         * 小于等于
         */
        LessEqual("<="),

        /**
         * 模糊匹配，即 like %pattern%
         */
        Like("LIKE"),

        /**
         * 不满足模糊匹配，即 not like %pattern%
         */
        NotLike("NOT LIKE"),

        /**
         * 以pattern开始，即 like pattern%
         */
        Begin("BEGIN", "BEGIN WITH"),

        /**
         * 不以pattern开始，即 not like pattern%
         */
        NotBegin("NOT BEGIN", "NOT BEGIN WITH"),

        /**
         * 以pattern结尾，即 like %pattern
         */
        End("END", "END WITH"),

        /**
         * 不以pattern结尾，即 not like %pattern
         */
        NotEnd("NOT END", "NOT END WITH"),

        /**
         * in条件
         */
        In("IN"),

        /**
         * not in 条件
         */
        NotIn("NOT IN"),

        /**
         * is null
         */
        IsNull("IS NULL"),

        /**
         * is not null
         */
        IsNotNull("IS NOT NULL"),

        /**
         * 范围条件，即 between start and end
         */
        Between("BETWEEN");

        private String[] values;

        Operator(String... values) {
            this.values = values;
        }

        public static Operator parse(String operator) {
            Assert.hasText(operator, "parameter 'operator' is required");

            operator = operator.trim();

            for (Operator enumOp : Operator.values()) {
                String[] opValues = enumOp.values;

                for (String val : opValues) {
                    if (val.equalsIgnoreCase(operator)) {
                        return enumOp;
                    }
                }
            }

            throw ExceptionFactory.createRuntimeException("pisces-core.Operator.parse-001", "{0} is invalid!",
                    operator);
        }

    }
}
